﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using TakePhotoTogether.Communication.Protocols;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace TakePhotoTogether.Communication
{
    /// <summary>
    /// Class responsible for management of Socket usage.
    /// Should be accessed using its singleton method.
    /// </summary>
    public class SocketManager
    {
        public Action OnConnectionSetup = null;
        public Action OnSocketDispose = null;

        private Dictionary<int, IRemoteDataListener> _remoteDataListeners = null;

        /// <summary> Singleton instance </summary>
        private static SocketManager _instance = null;

        protected readonly DataQueue dataQueue = null;
        private bool sendingLoopActivate = false;
        private bool receivingLoopActivate = false;

        private bool _connectionActive = false;

        private const string DEBUG_TAG = "TakePhotoTogether.Communication.SocketManager => ";

        private float[] byteCounter = new float[DataPacketFactory.MAX_PRIORITY_LEVEL];

        private ConnectionChannel connectionChannel;

        private bool isPacketReceivingRunning = false;

        protected SocketManager()
        {
            dataQueue = new WeightedFairDataQueue();

            for (int i = 0; i < DataPacketFactory.MAX_PRIORITY_LEVEL; i++)
            {
                byteCounter[i] = 0;
            }

            //Thread debugThread = new Thread(() =>
            //    {
            //        while (true)
            //        {
            //            for (int i = 0; i < DataPacketFactory.MAX_PRIORITY_LEVEL; i++)
            //            {
            //                Debug.WriteLine((byteCounter[i]/1000) + "KB for " + ((MessageTypes)(i+1)) + " packets");
            //            }
            //            Thread.Sleep(10000);
            //        }
            //    });
            //debugThread.IsBackground = true;
            //debugThread.Start();
        }

        /// <summary>
        /// Singleton method
        /// </summary>
        /// <returns></returns>
        public static SocketManager GetSocketManager()
        {
            if (_instance == null)
            {
                _instance = new SocketManager();
            }
            return _instance;
        }

        /// <summary>
        /// Add a listener to receive messages that come from socket
        /// TODO: Allow add more than one data listener to same messageType
        /// </summary>
        /// <param name="listener"> A class that implements IRemoteDataListener </param>
        /// <param name="messageType"> Filters which message will be forward to listener </param>
        public void AddDataListener(IRemoteDataListener listener, int messageType)
        {
            // Shouldn't add a key twice
            if (_remoteDataListeners == null)
            {
                _remoteDataListeners = new Dictionary<int, IRemoteDataListener>();
            }

            if (!_remoteDataListeners.ContainsKey(messageType))
            {
                _remoteDataListeners.Add(messageType, listener);
            }
        }

        /// <summary>
        /// Setup a socket allowing to start receive and send messages
        /// </summary>
        /// <param name="receivedSocket">Socket to be managed</param>
        public void SetupChannel(ConnectionChannel channel)
        {
            // Firstly, close old connection
            if (connectionChannel != null)
            {
                ClearQueue();
                DataPacket closePacket = DataPacketFactory.GetCloseAppPacket();

                connectionChannel.SendPacket(closePacket);
            }

            _connectionActive = false;

            // Update instance
            connectionChannel = channel;

            if (connectionChannel != null && connectionChannel.IsEnabled())
            {
                if (OnConnectionSetup != null)
                {
                    OnConnectionSetup();
                }

                connectionChannel.OnPacketReceived += OnPacketReceived;
                // Start loop of packets receiving
                if (!isPacketReceivingRunning)
                {
                    packetReceivingLoop();
                }
            }
        }

        private void OnPacketReceived(byte[] data, int messageType)
        {
            SendDataToListener(data, messageType);
        }

        /// <summary>
        /// Loop that handles received packets
        /// </summary>
        private async void packetReceivingLoop()
        {
            isPacketReceivingRunning = true;
            try
            {
                while (connectionChannel != null && connectionChannel.IsEnabled())
                {
                    Debug.WriteLine("SocketManager::PacketReceivingLoop");
                    await connectionChannel.ReadPacket();
                }
            }
            catch (Exception exception)
            {
                isPacketReceivingRunning = false;

                Debug.WriteLine(DEBUG_TAG + "error read: " + exception.HResult.ToString("X2") + " | " + exception.Message);
                if ((uint)exception.HResult == 0x80072745 ||
                    (uint)exception.HResult == 0x8000000B ||
                    (uint)exception.HResult == 0x80072746)
                {
                    //MessageBox.Show(ErrorDictionary.EXIT_FRIEND_MESSAGE, ErrorDictionary.EXIT_FRIEND_TITLE, MessageBoxButton.OK);
                    Dispose();
                }
                if ((uint)exception.HResult == 0x8007048F)
                {
                    //MessageBox.Show(ErrorDictionary.EXIT_FRIEND_MESSAGE, ErrorDictionary.EXIT_FRIEND_TITLE, MessageBoxButton.OK);
                }
                isPacketReceivingRunning = false;
            }

        }

        public void ClearQueue()
        {
            dataQueue.Clear();
        }

        public virtual void SendDataToListener(byte[] data, int messageType)
        {
            if (_remoteDataListeners != null && _remoteDataListeners[messageType] != null)
            {
                PayloadData payload = DataPacketFactory.GetPayload(messageType, data);

                _remoteDataListeners[messageType].ReceiveData(payload);
            }
       } 

        /// <summary>
        /// Loop that handles received packets
        /// </summary>
        protected virtual async Task packetSendingLoop()
        {
            if (sendingLoopActivate)
                return;

            sendingLoopActivate = true;
            try
            {
                while (dataQueue.Count() > 0 && connectionChannel != null && connectionChannel.IsEnabled())
                {
                    DataPacket packet = dataQueue.Dequeue();
                    await connectionChannel.SendPacket(packet);
                }
                sendingLoopActivate = false;
            }
            catch (Exception exception)
            {
                Debug.WriteLine(DEBUG_TAG + "error send: " + exception.HResult.ToString("X2") + " | " + exception.Message);
                sendingLoopActivate = false;

                if ((uint)exception.HResult == 0x80072745 ||
                    (uint)exception.HResult == 0x8000000B ||
                    (uint)exception.HResult == 0x80072746)
                {
                    Dispose();
                }
            }
        }

        /// <summary>
        /// Method to send data using socket
        /// </summary>
        /// <param name="packet"></param>
        /// <returns></returns>
        public async Task SendData(DataPacket packet)
        {
            dataQueue.Enqueue(packet);

            await packetSendingLoop();
        }

        /// <summary>
        /// Method to send data using socket
        /// </summary>
        /// <param name="packets"></param>
        /// <returns></returns>
        public async Task SendData(DataPacket[] packets)
        {
            int count = packets.Length;
            for (int p = 0; p < count; p++)
            {
                dataQueue.Enqueue(packets[p]);
            }

            await packetSendingLoop();
        }

        public int GetNumberOfPacketsOnQueue(int messageType)
        {
            return dataQueue.GetNumberOfPacketsOnQueue(messageType);
        }

        public int GetNumberOfPacketsOnQueue()
        {
            return dataQueue.GetNumberOfPacketsOnQueue();
        }

        /// <summary>
        /// Dispose socket connection
        /// <param name="callback"> action callback that must be called after dispose socket. If it is called then OnSocketDispose is not called </param>
        /// </summary>
        public void Dispose()
        {
            _connectionActive = false;

            Thread disposeConnection = new Thread(() =>
            {
                if (connectionChannel != null)
                {
                    connectionChannel.Dispose();
                    connectionChannel = null;

                    if (OnSocketDispose != null)
                    {
                        OnSocketDispose();
                    }
                }
            });
            disposeConnection.IsBackground = true;
            disposeConnection.Start();
        }

        public bool IsChannelEnabled()
        {
            bool result = false;

            if (connectionChannel != null)
            {
                result = connectionChannel.IsEnabled();
            }

            return result;
        }
    }

    public static class DataPacketSocketExtension
    {
        public static void Send(this DataPacket packet)
        {
            SocketManager.GetSocketManager().SendData(packet);
        }

        public static void Send(this DataPacket[] packets)
        {
            SocketManager.GetSocketManager().SendData(packets);
        }
    }
}
